<template>
	<el-dialog v-model="dialogVisible" title="预览" width="1000" @close="handleClose" @opened="onDialogOpened">
		<div ref="stlContainer" style="width: 100%; height: 700px"></div>
	</el-dialog>
</template>
<script setup>
	import { ref, onMounted, defineExpose, nextTick, shallowRef, onBeforeUnmount } from 'vue';
	import * as THREE from 'three';
	import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
	import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

	//是否显示弹窗
	const dialogVisible = ref(false);

	//cavas容器
	const stlContainer = ref(null);

	// 用于标记是否已经初始化过场景
	let isSceneInitialized = false;
	// 用于存储 animationFrame ID，停止动画时用
	let animationFrameId = null;

	// 使用 shallowRef 避免 Vue 代理 Three.js 对象的内部属性
	const scene = shallowRef(null); // 场景
	const camera = shallowRef(null); // 摄像机
	const renderer = shallowRef(null); // 渲染器
	const model = shallowRef(null); // 3D 模型对象
	let controls = null; // 控制器

	// 初始化 3D 场景
	const initScene = () => {
		// 如果 renderer 已经初始化，则不重复创建
		if (renderer.value) return;

		scene.value = new THREE.Scene();

		// 创建一个透视相机，参数分别为视野角度、宽高比、近裁剪面、远裁剪面
		camera.value = new THREE.PerspectiveCamera(75, window.innerWidth / 700, 0.1, 1000);
		// 设置相机的位置
		camera.value.position.z = 40;

		// 渲染器
		renderer.value = new THREE.WebGLRenderer();
		renderer.value.setSize(stlContainer.value.offsetWidth, stlContainer.value.offsetHeight); // 根据容器大小设置渲染器
		//将stlContainer的上个内容清空
		while (stlContainer.value.firstChild) {
			stlContainer.value.removeChild(stlContainer.value.firstChild);
		}
		//直接赋值
		stlContainer.value.appendChild(renderer.value.domElement);

		//点光源
		const point1 = new THREE.PointLight(0xffffff);
		scene.value.add(point1);
		const point = new THREE.PointLight(0xffffff);
		scene.value.add(point); //点光源添加到场景中
		// 创建环境光
		const light = new THREE.AmbientLight(0x444444, 5);
		// 将环境光添加到场景中
		scene.value.add(light);
		// 创建方向光
		const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
		// 将方向光添加到场景中
		scene.value.add(directionalLight);

		// 创建控制器：使模型可拖动旋转、鼠标滚轮缩放
		controls = new OrbitControls(camera.value, renderer.value.domElement);
		controls.enableDamping = true; // 开启阻尼效果
		controls.dampingFactor = 0.25; // 阻尼系数

		// 渲染循环
		const animate = () => {
			// 使用requestAnimationFrame函数来循环调用animate函数
			animationFrameId = requestAnimationFrame(animate);
			// 如果模型存在
			if (model.value) {
				// 更新控制器
				controls.update();
			}
			// 使用渲染器将场景和相机渲染出来
			renderer.value.render(scene.value, camera.value);
		};

		animate();
	};

	// 加载在线 STL 文件
	const loadStlFromUrl = (url) => {
		// 在加载新模型之前移除旧模型
		if (model.value) {
			scene.value.remove(model.value); // 从场景中移除旧模型
			model.value.geometry.dispose(); // 释放几何体资源
			model.value.material.dispose(); // 释放材质资源
			model.value = null; // 将模型设置为 null
		}
		const loader = new STLLoader();
		// 使用 fetch 获取文件内容
		fetch(url)
			.then((response) => response.arrayBuffer()) // 获取二进制数据
			.then((data) => {
				const geometry = loader.parse(data);
				const material = new THREE.MeshStandardMaterial({
					overdraw: true,
					color: 0xffffff,
					shading: THREE.FlatShading
				});
				model.value = new THREE.Mesh(geometry, material);
				scene.value.add(model.value); // 将模型加入场景
			})
			.catch((error) => {
				console.error('加载 STL 文件失败:', error);
			});
	};

	/**打开弹窗 */
	const openDialog = (item) => {
		dialogVisible.value = true;
		nextTick(() => {
			if (!isSceneInitialized) {
				initScene(); // 仅在第一次打开时初始化场景
				isSceneInitialized = true;
			}
			loadStlFromUrl(item.url);
		});
	};

	// 关闭弹窗时清理资源
	const handleClose = () => {
		dialogVisible.value = false;
		// 清理 Three.js 资源
		if (model.value) {
			scene.value.remove(model.value);
			model.value.geometry.dispose();
			model.value.material.dispose();
			model.value = null;
		}
		controls.value = null;
		renderer.value = null;
		scene.value = null;
		renderer.value = null;
		model.value = null;
		stlContainer.value = null;
		isSceneInitialized = false;
		// 取消动画循环
		if (animationFrameId) {
			cancelAnimationFrame(animationFrameId); // 停止动画
			animationFrameId = null;
		}
	};

	// 监听弹窗打开后调整渲染器的大小
	const onDialogOpened = () => {
		// 确保在弹窗打开后渲染器尺寸适应
		resizeRenderer();
		window.addEventListener('resize', resizeRenderer);
	};

	// 监听弹窗关闭时清理 resize 事件
	onBeforeUnmount(() => {
		window.removeEventListener('resize', resizeRenderer);
	});

	// 渲染器尺寸更新
	const resizeRenderer = () => {
		if (renderer.value && stlContainer.value) {
			renderer.value.setSize(stlContainer.value.offsetWidth, stlContainer.value.offsetHeight);
			camera.value.aspect = stlContainer.value.offsetWidth / stlContainer.value.offsetHeight;
			camera.value.updateProjectionMatrix();
		}
	};

	//暴露方法
	defineExpose({
		openDialog
	});
</script>
<style lang="scss" scoped></style>
